/*
 ********************************************************************************
 *
 * File Name    : 'main_watchdog.c'
 * Title        : Transmitter/Watchdog Firmware for x433 MHz
 * Author       : Martin Metal
 * Date         : 17/06/2010
 * Revised      : 17/06/2010
 * Version      : 1.0
 * Target MCU   : Atmel AVR ATMega8
 * Editor Tabs  : 3
 * Description  : Implementation of the firmware for the Transmitter/Watchdog.
 *                The firmware combines the transmitter and the independently
 *                working watchdog. The watchdog can use the same sensors that
 *                the transmitter uses and additionally provides facility for
 *                USB configuration, optical/acoustic alarm for capacity drop,
 *                and logging. The device still can be used in the mode as 
 *                classic transmitter and avoiding the watchdog function.
 *                establishes and maintain the radio link between receiver and 
 *                transmitter defines the communication protocol used to transfer 
 *                the payload data. The firmware detects the telemetry values
 *                that are then packed inside the specific protocol.
 *
 ********************************************************************************
 */

#include "translink.h"

uint8_t ee_dev_addr __attribute__((section(".eeprom"))) = 0x12;;
/* Define the persistent configuration items. The persistence
 * is achieved by storing the values in the internal EEPROM
 * on the uController. ATMega8 has 512 bytes of on-chip EEPROM.
 */

struct stack_t {
	uint8_t payload[5];
	uint16_t voltage;
	uint16_t capacity;
	uint16_t current;
	uint16_t rpm;
	uint32_t ucapa;
	uint16_t tmp;
	uint16_t pulses;
};
typedef struct stack_t stack_t;


struct adcstack_t {
	uint16_t current;
};
typedef struct adcstack_t adcstack_t;


struct nstack_t {
	uint16_t voltage;
	uint16_t capacity;
	uint16_t current;
	uint16_t rpm;
};
typedef struct nstack_t nstack_t;
	


struct counters_t {
	uint8_t transmission;
	uint8_t alarm_freq;
	uint16_t revolutions;
	uint16_t capacity;
};
typedef struct counters_t counters_t;


struct flags_t {
	uint8_t transmission_semaphore:1;
	uint8_t write_eeprom:1;
	uint8_t write_alarm:1;
	uint8_t write_transmitter:1;
	uint8_t alarm_invalidated:1;
};
typedef struct flags_t flags_t;




/* ATTENTION
 * ---------
 * The data structures must be declared volatile
 * otherwise they are not visible inside the interrupt 
 * routines.
 */
volatile stack_t stack;
volatile flags_t flags;
volatile counters_t counters;
volatile uint8_t sampling_counter;

static uint8_t buffer[13];
static uint8_t device_address;
static uint16_t log_cntr;

uint16_t oop_voltage;
uint16_t startup_voltage;
uint16_t power_source_capacity;
uint16_t ttemp;
uint16_t	address;
uint16_t	percent;

uint8_t incoming, status;

uint8_t	sampling_rate;

nstack_t normalized_stack;
adcstack_t adc_stack;
hall_sensor_t hall_sensor;


/* Interrupt routine for internal timer T1/C1
 * ------------------------------------------
 * The interrupt is triggered every 20 ms. The interval is configured by
 * pre scaling the counter TCNT1. The counting frequency uses the system
 * clock and pre scales it by factor 1024. The interrupt handler resets
 * the counter and counting starts from 0 again.
 *
 * The interrupt handler calculates the consumed capacity within the 20 ms,
 * updates the various internal counters and transmits the synchronization
 * signal (carrier signal of the used protocol).
 *
 * Handler also modules the buzzer and beacon frequency. The frequency 
 * is set to 2 Hz and can be controlled by changing the counting limit
 * in define WD_ALARMFREQUENCY.
 */
ISR(TIMER1_COMPA_vect)
{ // <<<
	oc1_interrupt_disable();

	/*
	 * Calculate the uAh in the 20ms cycle and update the counters.
	 */
	stack.ucapa += (uint32_t) (stack.current * 1000 / CAPACITY_CONVERSION);
	counters.capacity++;
	counters.revolutions++;

	/*
	 * The transmission takes place only if the communication automat
	 * is not blocked by another ongoing transmission.
	 */
	if( flags.transmission_semaphore != 1 )
  	{
		flags.transmission_semaphore ^= 1;
		USART_send_byte( SYN );
		counters.transmission++;
		flags.transmission_semaphore ^= 1; 
	} 

	/* Buzzer/Beacon control. The alarm generates 2 Hz 
	 * beeping/blinking signal. The internal buzzer frequency 
	 * is 2300 Hz, is controlled by PWM via T2. This
	 * handler adds 2 Hz square modulation.
	 */
	if( flags.alarm_invalidated == 1 )
	{
		if( counters.alarm_freq++ >= WD_ALARMFREQUENCY )
		{
			counters.alarm_freq = 0;
			if( flags.write_alarm == 1 )
				alarm( ON );
			else 
				alarm( OFF );
			flags.write_alarm ^= 1;
		}
	}

	oc1_interrupt_enable();
} // >>>


/* Interrupt routine for external interrupt INT1
 * -----------------------------------------------
 * Interrupt service routine updates the RPM counter.
 * The HW interrupt is generated by pulling the signal
 * line to zero by the used Hall-effect sensor. The sensor
 * integrates the Schmidt circuity therefore the sensor
 * is directly connected to the ATMega8 ports. The counting
 * continues for the defined interval, afterwards is 
 * calculated the RPM and scheduled for the next transmission. 
 * (1,5 second).
 */ 
ISR(INT1_vect)
{ // <<<
	stack.pulses++;
} // >>>


int main( void )
{
	/* 
	 * Determine the application mode. If the Ucc is takes from USB port,
	 * then the firmware automatically enters the USB mode. In the case
	 * that the Ucc comes from internal stabilizer the application 
    * enters normal mode.
	 */	 
	HWMODE_CTRL &= ~ _BV(USB_MODE); // PD5 digital input
	HWMODE_PORT &= ~ _BV(USB_MODE); // turn off the pull-up resistor on PIN D5

	if( bit_is_set( HWMODE_PIN, USB_MODE ))
	{
		app_mode = MODE_USB_INIT;
	}
	else
	{
		/* Assuming the application is not connected to USB port,
		 * the application continues to check the connected interfaces.
		 * The SPI slave is a special application mode, therefore the
		 * check happens before entering main message pump.
		 */
		SPI_CTRL &= ~_BV(SPI_SS); // digital input SS Pin 
		SPI_PORT |= _BV(SPI_SS); // turn on the pull-up resistor on pin SS
		_delay_us( 300 );
		if( bit_is_set( SPI_PIN, SPI_SS )) 
		{
			app_mode = MODE_LOCAL_INIT;
		}
		else
		{
			app_mode = MODE_SPI_SLAVE_INIT;
		}
	}

	/*********** HERE BEGINS THE MAIN APPLICATION BLOCK ***********/

	while( 1 )
	{
		switch( app_mode )
		{
			case MODE_USB_INIT:
				/*
				 * Initialize the USB mode.
				 * USB routines requires lots of resources
				 * and therefore the implementation clearly
				 * separates two modes. The USB mode only
				 * controls the USB communication. The reset
				 * of the application is the only way to
				 * leave the USB mode. 
				 */
				
				cli();
				usbInit();
				usbDeviceDisconnect(); 
				_delay_ms(300);			
				usbDeviceConnect();
				sei();
				/* 
				 * enforce re-enumeration, do this while
				 * interrupts are disabled! And wait
				 * for at least 250ms for PC-host to complete
				 * the initialization cycle
				 */

				app_mode = MODE_USB_MAIN;

			break;

			case MODE_USB_MAIN:
				/* 
				 * Poll the USB status and directly inside the 
				 * USB Control function process the received data.
				 * The only operation outside the service routine
				 * the the EEPROM management
				 */

				usbPoll();

				/*
				 * TODO: Process here the data sent by USB buss, if needed
				 *       alternatively process the data in the usbControl.
				 */

			break;	

		}
		sleep_mode();
	}

	/*********** HERE ENDS THE MAIN APPLICATION BLOCK ***********/
}



void oc1_init( void )
{
	unsigned char sreg;

	// enable PIN OC1A jako toggle vystupni pin pri compare match
	//OC1A_DDR |= _BV(OC1A_PIN);

	// enable toggle OC1A output on compare match
	// TCCR1A = _BV(COM1A0);

	// use CLK/1024 prescale value, clear timer/counter on compareA match			       
	TCCR1B = _BV(CS10) | _BV(CS12) | _BV(WGM12);
    
	sreg = SREG;
	cli();
	
		// Set the Timer/counter1 to zero
		TCNT1 = (uint16_t) 0;

		// Set the compare register counter to pre scaled value. 
		// The compare interrupt is called every 20 ms
		//OCR1A = (uint16_t) (F_CPU/4/64/TIMER_CLOCK) - 1; 
		OCR1A = (uint16_t) WD_TIMERPRESCALER;

	SREG = sreg;
	sei();
}


void oc1_interrupt_disable( void )
{
	// Disable interrupt called by compare match.
	TIMSK &= ~_BV(OCIE1A);
}


void oc1_interrupt_enable( void )
{
	// Enable interrupt called by compare match.
	TIMSK |= _BV(OCIE1A);
}


void adc_init( void )
{
	/*
 	 * enable ADC, select ADC clock = F_CPU / 128 (115 kHz)
	 */
	ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
}


void adc_set_input_pin( uint8_t pin )
{
	ADMUX = pin;
}


uint16_t adc_average_w( uint16_t sample, uint16_t vector )
{
	if( vector == 0 )
		return sample;
	return( ((2*vector)+sample)/3 );
}


void irq_init( void )
{
	/*
	 *	Configure IRQ1
	 */
	MCUCR &= ~_BV(ISC10); 
	MCUCR |= _BV(ISC11); 
	GICR |= _BV(INT1); 
}


uint16_t normalize( uint16_t data, normalizer_t n )
{
	uint16_t res;
	
	switch( n )
	{
		case CAPACITY:
			res = data / ADC_C_INT;
			break;

		case RPM:
			res = data / ADC_R_INT;
			break;

		case VOLTAGE:
		case CURRENT:
		default:
			res = data;
			break;
	}
	return res;
}



uint16_t denormalize( uint16_t data, normalizer_t n )
{
	uint16_t res;

	switch( n )
	{
		case VOLTAGE:
		case CURRENT:
			res = (data * ADC_TX_U_FRAC2) / 100;
			res += (data * ADC_TX_U_FRAC1) / 10;
			res += (data * ADC_TX_U_INT);
			break;

		case CAPACITY:
			res = data * ADC_C_INT;
			break;

		case RPM:
			res = data * ADC_R_INT;
			break;

		default:
			res = data;
	}
	return res;
}


uint16_t current_from_hall_sensor( uint16_t data, hall_sensor_t* sensor )
{
	uint16_t res;

	if( data < (HALL_SENSOR_CENTRE + sensor->const_b) )
		return( 0 );

	res = data - ( HALL_SENSOR_CENTRE + sensor->const_b);
	res = (((res * 10 ) / sensor->const_a ) + 5 ) / 10;

	return res;
}


void oc2_init( void )
{
	/*
	 * Configure OC2 pin as output pin.
	 * set the phase correct PWM mode (WGM20),
	 * set pre-scale factor to 32 | 8 (based on CS2[2-0] configuration)
	 * set the internal clocks (asynchronous mode).
	 */

	ALARM_CTRL |= _BV(ALARM);

//	TCCR2 = _BV(WGM20) | _BV(COM21) | _BV(CS21) | _BV(CS20);
	TCCR2 = _BV(WGM20) | _BV(COM21) | _BV(CS21);
	ASSR &= ~_BV(AS2);

	alarm( OFF );
}


void alarm( enum ALARM_MODE mode )
{
	switch( mode )
	{
	case OFF:
		OCR2 = 0;
		break;
	case ON:
		OCR2 = 127;
		break;
	case LOW:
		OCR2 = 64;
		break;
	case HIGH:
		OCR2 = 192;
		break;
	default:
		break;
	}
} 


void beep( enum ALARM_MODE mode, uint8_t count, enum ALARM_LENGTH length )
{
	while( count-- ) {
		alarm( (enum ALARM_MODE) mode );
		switch( length )
		{
		case SHORT:
			_delay_ms( 200 );
			alarm( (enum ALARM_MODE) OFF);
			_delay_ms( 150 );
		break;
		case LONG:
			_delay_ms( 500 );
			alarm( (enum ALARM_MODE) OFF);
			_delay_ms( 500 );
		break;
		}
	}
}



/* -------------------------------------------------------------------------
 * USB Implementation.
 * -------------------------------------------------------------------------
 * The USB mode uses the control end-point 0 only. This function implements
 * the essential control commands supported by the device. The control 
 * end-point 0 enables the transmission of max 254 bytes in one transmission
 * and therefore the log file retrieval requires specific handling.
 */
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
	uint16_t			temp;
	usbRequest_t    *rq = (void *)data;

	switch( rq->bRequest ) {
		case CUSTOM_RQ_SEND_CONTROLL_CHAR:
   	   buffer[0] = (uint8_t) rq->wValue.bytes[0];
			eeprom_write_byte( &ee_dev_addr, (uint8_t) buffer[0] );
			return 0;						// tell driver how many data send back
		
		case CUSTOM_RQ_GET_DEVICE_VERSION:
			buffer[0] = ((uint32_t) DEVICE_VERSION & 0xFF);
			buffer[1] = (((uint32_t) DEVICE_VERSION >> 8) & 0xFF);
			buffer[2] = (((uint32_t) DEVICE_VERSION >> 16) & 0xFF);
			buffer[3] = (((uint32_t) DEVICE_VERSION >> 24) & 0xFF);
			usbMsgPtr = buffer;
			return 4;
		
		default:
			return 0;
	}
}
